package com.roadmap.client.module.task;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;

import com.roadmap.client.module.BaseManager;
import com.roadmap.common.BaseConst;
import com.roadmap.common.BaseEntity;
import com.roadmap.common.BaseUtility;
import com.roadmap.common.RdcLogger;
import com.roadmap.common.RdcResourceBundle;
import com.roadmap.common.BaseEnum.ActionType;
import com.roadmap.entities.Aspirant;
import com.roadmap.entities.LifeGoal;
import com.roadmap.entities.LifePlan;
import com.roadmap.entities.LifeTask;
import com.roadmap.entities.Notice;
import com.roadmap.entities.ext.TaskExt;
import com.roadmap.exception.ActionNotSupportedException;
import com.roadmap.exception.EntityNotFoundException;
import com.roadmap.exception.InvalidActionException;
import com.roadmap.exception.InvalidQueryException;
import com.roadmap.exception.InvalidValueException;
import com.roadmap.exception.NullObjectException;
import com.roadmap.exception.ServerException;
import com.roadmap.repo.db.RdcQuery;

public class TaskManager extends BaseManager {
	private final static Logger _log = RdcLogger.getLogger(TaskManager.class);

	public TaskManager(String aspirantId, LifePlan parent) {
		super(aspirantId, LifeTask.class, parent);
	}

	public TaskManager(String aspirantId, Aspirant parent) {
		super(aspirantId, LifeTask.class, parent);
	}

	public TaskManager(String aspirantId) {
		super(aspirantId, LifeTask.class);
	}

	public TaskManager(String aspirantId, String planId) {
		super(aspirantId, LifeTask.class, LifePlan.class, planId);
	}

	/**
	 * save a new task to database.
	 */
	public void create(BaseEntity entity) throws NullObjectException,
			InvalidActionException, InvalidValueException, ServerException,
			EntityNotFoundException, ActionNotSupportedException {
		if (checkEntityIsValid(entity) == false) {
			return;
		}

		saveTask((LifeTask) entity);
	}

	private void saveTask(LifeTask task) throws ServerException,
			InvalidActionException, InvalidValueException,
			EntityNotFoundException, ActionNotSupportedException {
		if (checkEntityIsValid(task) == false) {
			return;
		}

		if (task.getGoalId() == null) {
			getDbUtil().save(task);
			getNoticeManager().notifyForEntry(task, ActionType.CREATE, null);
		}
		else {
			// ensure the task belong to a plan and its goal's status should be
			// SCHEDULED or UNDERGOING
			LifePlan plan = null;
			if (getParent() instanceof LifePlan) {
				plan = (LifePlan) getParent();
			}
			else {
				_log
						.warn("the task's parent should be a plan if its plan id is not null.");
				plan = (LifePlan) createParent(LifePlan.class, task.getGoalId());
				// setParent(plan);
			}
			if (plan != null) {
				LifeGoal goal = (LifeGoal) getDbUtil().getEntityById(
						LifeGoal.class, plan.getGoalId());
				if (checkIfGoalIsValid(goal)) {
					getDbUtil().save(task);
					getNoticeManager().notifyForEntry(task, ActionType.CREATE,
							null);
				}
				else {
					String msg = "the goal (" + goal.getId()
							+ ")'s status doesn't support current action.";
					ActionNotSupportedException ex = new ActionNotSupportedException();
					_log.error(msg, ex);
					throw ex;
				}
			}
			else {
				String msg = "no plan was found for task: " + task.getId();
				EntityNotFoundException enfe = new EntityNotFoundException();
				_log.error(msg, enfe);
				throw enfe;
			}
		}
	}

	/**
	 * if the goal is invalid, exception will be thrown
	 * 
	 */
	private boolean checkIfGoalIsValid(LifeGoal goal)
			throws EntityNotFoundException, ActionNotSupportedException {
		if (goal == null) {
			String msg = "No goal was found";
			EntityNotFoundException enfe = new EntityNotFoundException();
			_log.error(msg, enfe);
			throw enfe;
		}
		else if (goal.getStatus() != BaseConst.LIFEGOAL_STATUS_UNDERGOING
				&& goal.getStatus() != BaseConst.LIFEGOAL_STATUS_SCHEDULED) {
			/*
			 * when the task's status is SCHEDULED or UNDERGOING, the goal's
			 * status should be the same, or else it is invalid and an exception
			 * will be thrown
			 */
			String msg = "The goal (" + goal.getId()
					+ ")'s status doesn't support current action.";
			ActionNotSupportedException ex = new ActionNotSupportedException();
			_log.error(msg, ex);
			throw ex;
		}

		return true;
	}

	/**
	 * if the entity is a task, don't really delete it from database, just
	 * change its status to DELETED, or else delete it from database
	 */
	public void delete(BaseEntity entity) throws NullObjectException,
			InvalidActionException, InvalidValueException, ServerException,
			EntityNotFoundException, ActionNotSupportedException {
		if (checkEntityIsValid(entity) == false) {
			return;
		}

		// temporary solution
		changeTaskStatus(entity, BaseConst.LIFETASK_STATUS_DELETED);
	}

	/**
	 * Only a CANCLED task supports this action. The target status should not be
	 * DELETED, CANCLED or COMPLTED.
	 */
	@Deprecated
	public void restore(BaseEntity entity, int targetStatus)
			throws ActionNotSupportedException, InvalidValueException,
			InvalidActionException, ServerException, EntityNotFoundException {
		if (checkEntityIsValid(entity) == false) {
			return;
		}

		LifeTask task = (LifeTask) entity;
		if (task.getStatus() != BaseConst.LIFETASK_STATUS_CANCELED) {
			ActionNotSupportedException ane = new ActionNotSupportedException();
			_log.error("Only a CANCLED task supports this action.", ane);
			throw ane;
		}

		if (targetStatus == BaseConst.LIFETASK_STATUS_COMPLETED
				|| targetStatus == BaseConst.LIFETASK_STATUS_CANCELED) {
			ActionNotSupportedException ise = new ActionNotSupportedException();
			String errorMsg = "The task can't be restored to the status COMPLETED or CANCLED.";
			_log.error(errorMsg, ise);
			throw ise;
		}

		changeTaskStatus(entity, targetStatus);
	}

	/**
	 * update the status of a task and also its updated time. If going to change
	 * the status to COMPLETED, ensure the task's current status is UNDERGOING
	 */
	private void changeTaskStatus(BaseEntity entity, int status)
			throws NullObjectException, InvalidActionException,
			InvalidValueException, ServerException, EntityNotFoundException {
		LifeTask task = (LifeTask) entity;
		task.setStatus(status);
		Date dt = new Date();
		task.setUpdatedTime(dt);
		if (status == BaseConst.LIFETASK_STATUS_COMPLETED) {
			task.setRealEndDate(dt);
		}
		getDbUtil().update(task);
	}

	/**
	 * to complete a task, change its status to COMPLETED.Only a UNDEGOING task
	 * supports this action.
	 */
	public void complete(BaseEntity entity) throws NullObjectException,
			InvalidActionException, InvalidValueException, ServerException,
			EntityNotFoundException, ActionNotSupportedException {
		if (checkEntityIsValid(entity) == false) {
			return;
		}

		LifeTask task = (LifeTask) entity;
		if (task.getStatus() != BaseConst.LIFETASK_STATUS_UNDERGOING) {
			ActionNotSupportedException ane = new ActionNotSupportedException();
			_log.error("Only a UNDEGOING task supports this action.", ane);
			throw ane;
		}

		changeTaskStatus(entity, BaseConst.LIFETASK_STATUS_COMPLETED);
		getNoticeManager().notifyForEntry(entity, ActionType.COMPLETE, null);
	}

	/**
	 * to cancel a task, change its status to CANCLED. A COMPLETED or CANCLED
	 * task doesn't support this action.
	 * 
	 * @throws ActionNotSupportedException
	 */
	public void cancel(BaseEntity entity) throws NullObjectException,
			InvalidActionException, InvalidValueException, ServerException,
			EntityNotFoundException, ActionNotSupportedException {
		if (checkEntityIsValid(entity) == false) {
			return;
		}

		LifeTask task = (LifeTask) entity;
		if (task.getStatus() == BaseConst.LIFETASK_STATUS_COMPLETED
				|| task.getStatus() == BaseConst.LIFETASK_STATUS_CANCELED) {
			ActionNotSupportedException ane = new ActionNotSupportedException();
			_log.error(
					"A COMPLETED or CANCLED task doesn't support this action.",
					ane);
			throw ane;
		}

		changeTaskStatus(entity, BaseConst.LIFETASK_STATUS_CANCELED);
	}

	/**
	 * The entity should not be NULL and its type is LifeTask. Also it should
	 * belong to the manager's parent, e.g. a task parent may be a plan or a
	 * aspirant. if the task doesn't belong to a plan, its parent should be a
	 * aspirant. And the task's status should not be DELETED.
	 * 
	 * @throws InvalidValueException
	 */
	protected boolean checkEntityIsValid(BaseEntity entity)
			throws InvalidValueException {
		String errorMsg = null;
		if (entity instanceof LifeTask) {
			LifeTask task = (LifeTask) entity;
			if (task.getStatus() == BaseConst.LIFETASK_STATUS_DELETED) {
				errorMsg = "It is not allowed to perform any action on a DELETED task.";
			}
			else if ((getParent() instanceof LifePlan)
					&& task.getGoalId() != null) {
				return task.getGoalId().equals(getParent().getId());
			}
			else if ((getParent() instanceof Aspirant) && getOwnerId() != null
					&& task.getAspirantId() != null) {
				return task.getAspirantId().equals(getOwnerId());
			}
			else {
				errorMsg = "The child entity is not valid, or the manager's parent is not valid.";
			}
		}

		InvalidValueException ive = new InvalidValueException();
		_log.warn(errorMsg, ive);
		throw ive;
	}

	/** @return: a list of TaskResult elements */
	public List<BaseEntity> getEntitiesInDateScope(Date first, Date last)
			throws InvalidValueException, InvalidActionException,
			ServerException {
		if (first == null || last == null || first.after(last)) {
			InvalidValueException ive = new InvalidValueException();
			String msg = "Invalid dates. The first date should be before the second one";
			_log.error(msg, ive);
			throw ive;
		}

		// List<SimpleResult> rslist = null;
		if (first.equals(last))
			return getTasksInDay(first);
		else
			return getSpecifiedTasks(first, last);

		// return rslist;
	}

	private List<BaseEntity> getSpecifiedTasks(Date first, Date last)
			throws NullObjectException, InvalidActionException,
			InvalidValueException, ServerException {
		// find out the tasks whose scheduled start date and scheduled end date
		// between the specific dates
		String firstStrs = BaseUtility.formatDate(first);
		String lastStrs = BaseUtility.formatDate(last);
		_log.debug("first date: " + firstStrs);
		_log.debug("last date: " + lastStrs);

		StringBuffer criteria = new StringBuffer(" aspirantId='");
		criteria.append(getOwnerId());
		criteria.append("' and status<>");
		criteria.append(BaseConst.LIFETASK_STATUS_DELETED);
		criteria.append(" and scheduledStartDate between'");
		criteria.append(firstStrs);
		criteria.append("' and '");
		criteria.append(lastStrs);
		criteria.append("'))");

		return getDbUtil().getEntitiesByCriteria(LifeTask.class,
				criteria.toString());
	}

	private List<BaseEntity> getTasksInDay(Date date)
			throws NullObjectException, InvalidActionException,
			InvalidValueException, ServerException {
		String dt = BaseUtility.formatDate(date);
		StringBuffer criteria = new StringBuffer();
		criteria.append("status<>");
		criteria.append(BaseConst.LIFETASK_STATUS_DELETED);
		criteria.append(" and scheduledStartDate='");
		criteria.append(dt);
		criteria.append("' and aspirantId='");
		criteria.append(getOwnerId());
		criteria.append("' order by startTimeSlice ASC");

		return getDbUtil().getEntitiesByCriteria(LifeTask.class,
				criteria.toString());
	}

	@Override
	public List<BaseEntity> getEntitiesByStatus(int[] states)
			throws ActionNotSupportedException, InvalidValueException,
			InvalidQueryException, ServerException {
		return obtainEntitiesByStatus(states);
	}

	@Deprecated
	public List getExecutableTasks() throws ActionNotSupportedException,
			InvalidQueryException, InvalidValueException, ServerException {
		int[] states = { BaseConst.LIFETASK_STATUS_SCHEDULED,
				BaseConst.LIFETASK_STATUS_UNDERGOING };
		return obtainEntitiesByStatus(states);
	}

	@Override
	@Deprecated
	public String getEntityHealthy(BaseEntity entity)
			throws ActionNotSupportedException, InvalidValueException {
		if (!checkEntityIsValid(entity)) {
			throw new InvalidValueException();
		}

		LifeTask task = (LifeTask) entity;
		int status = task.getStatus();
		// only when status is undergoing or scheduled, validation is needed
		if (status == BaseConst.LIFETASK_STATUS_SCHEDULED
				|| status == BaseConst.LIFETASK_STATUS_UNDERGOING) {
			Date today = new Date();
			Date start = task.getScheduledStartDate();
			Date end = task.getScheduledEndDate();
			// if the start time is before today but the status is still
			// Scheduled
			if (status == BaseConst.LIFETASK_STATUS_SCHEDULED
					&& start.before(today)) {
				return RdcResourceBundle.Entity_Symptom_Not_Started;
			}

			if (status == BaseConst.LIFETASK_STATUS_UNDERGOING
					&& end.before(today)) {
				return RdcResourceBundle.Entity_Symptom_Not_Ended;
			}
		}

		return RdcResourceBundle.Entity_Symptom_Healthy;
	}

	/** diagnose the health of the task */
	private String diagnose(LifeTask task, Date ref) {
		if (task == null || ref == null) {
			_log.warn("NULL inputs for task or the date");
			return null;
		}

		int status = task.getStatus();
		// only when status is undergoing or scheduled, validation is needed
		if (status == BaseConst.LIFETASK_STATUS_SCHEDULED
				|| status == BaseConst.LIFETASK_STATUS_UNDERGOING) {
			Date start = task.getScheduledStartDate();
			Date end = task.getScheduledEndDate();
			// if the start time is before today but the status is still
			// Scheduled
			if (status == BaseConst.LIFETASK_STATUS_SCHEDULED
					&& start.before(ref)) {
				return RdcResourceBundle.Entity_Symptom_Not_Started;
			}

			if (status == BaseConst.LIFETASK_STATUS_UNDERGOING
					&& end.before(ref)) {
				return RdcResourceBundle.Entity_Symptom_Not_Ended;
			}
		}

		return RdcResourceBundle.Entity_Symptom_Healthy;
	}

	@Override
	public List<BaseEntity> getEntitiesOfAspirant() throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		/** modified on 2010-1-7 */
		String hql = "from LifeTask where aspirantId=? and status<>? order by createdTime DESC";
		Object[] params = { getOwnerId(), BaseConst.LIFETASK_STATUS_DELETED };
		return (List<BaseEntity>) getDbUtil().executeHQLQuery(hql, params);
	}

	@Override
	public List<BaseEntity> getEntitiesOfParent() throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		// String criteria = getParentIdName() + "='" + getParent().getId()
		// + "' and status<>5 order by createdTime DESC";
		// return (List<BaseEntity>) getDbUtil().getEntitiesByCriteria(
		// getManagedEntityType(), criteria);
		/** modified on 2010-1-7 */
		String hql = null;
		if (getParent() instanceof LifePlan) {
			hql = "from LifeTask where planId=? and status<>? order by createdTime DESC";
		}
		else {
			hql = "from LifeTask where aspirantId=? and status<>? order by createdTime DESC";
		}
		Object[] params = { getParent().getId(),
				BaseConst.LIFETASK_STATUS_DELETED };
		return (List<BaseEntity>) getDbUtil().executeHQLQuery(hql, params);
	}

	/**
	 * get the executable tasks whose statuses are scheduled or undergoing.
	 */
	@Override
	public List<TaskExt> getExecutableEntities() throws HibernateException,
			ServerException {
		// get the tasks
		String sql = "select {task.*}, plan.name from (select t.* from lifetask as t where t.aspirantId=? and (t.status=? or t.status=?)) as task left join lifeplan as plan on task.planId=plan.planId order by task.createdTime DESC";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("task", LifeTask.class);
		query.addScalar("name");
		query.setParameters(new Object[] { getOwnerId(),
				BaseConst.LIFETASK_STATUS_SCHEDULED,
				BaseConst.LIFETASK_STATUS_UNDERGOING });
		List list = query.getResults();
		if (list == null || list.isEmpty()) {
			return null;
		}

		List<TaskExt> taskExts = new ArrayList<TaskExt>();
		String[] taskIDs = new String[list.size()];
		int index = 0;
		Iterator iter = list.iterator();
		Date today = new Date();
		while (iter.hasNext()) {
			Object[] result = (Object[]) iter.next();
			LifeTask task = (LifeTask) result[0];
			taskIDs[index++] = task.getId();

			TaskExt ext = new TaskExt(task);
			ext.setGoalName((String) result[1]);
			taskExts.add(ext);

			// diagnoze the task
			String symptom = diagnose(task, today);
			ext.setSymptom(symptom);
		}

		// query the statistic data of tasks
		String hql = "select taskId, count(id), sum(executeTimeLength), sum(proportionOfTask), max(createdTime) from Execution where taskId in(:taskIDs) group by taskId";
		List results = getDbUtil().executeHQLQeury(hql, "taskIDs", taskIDs);
		if (results != null && !results.isEmpty()) {
			for (int i = taskExts.size() - 1; i >= 0; i--) {
				TaskExt ext = taskExts.get(i);
				for (int j = 0; j < results.size(); j++) {
					Object[] result = (Object[]) results.get(j);
					String taskId = (String) result[0];
					if (ext.getId().equals(taskId)) {
						ext.setExecCount(((Long) result[1]).intValue());
						ext.setTatalExecTime(((Long) result[2]).intValue());
						ext.setProgress(((Long) result[3]).intValue());
						ext.setLatestExecDate((Date) result[4]);

						results.remove(j); // if this node was hit, it wouldn't
						// be next time
						break;
					}
				}
			}
		}

		return taskExts;
	}

	@Override
	public TaskExt getEntityExt(String entityId) throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		if (entityId == null) {
			InvalidValueException e = new InvalidValueException(
					"NULL entity Id");
			_log.error(e);
			throw e;
		}
		// query in the task table
		String sql = "select {task.*}, plan.name from (select t.* from lifetask as t where t.taskId=:tid and t.status<>:state and t.aspirantId=:uid) as task left join lifeplan as plan on task.planId=plan.planId";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("task", LifeTask.class);
		query.addScalar("name");
		query.setParameter("uid", getOwnerId());
		query.setParameter("tid", entityId);
		query.setParameter("state", BaseConst.LIFETASK_STATUS_DELETED);
		Object[] result = (Object[]) query.getUniqueResult();
		if (result == null) {
			return null;
		}
		TaskExt task = new TaskExt((LifeTask) result[0]);
		task.setGoalName((String) result[1]);

		// compute
		String hql = "select count(id), sum(executeTimeLength), sum(proportionOfTask), max(createdTime) from Execution where taskId=?";
		result = (Object[]) getDbUtil().getUniqueEntity(hql,
				new Object[] { entityId });
		if (result != null) {
			task.setExecCount(((Long) result[0]).intValue());
			// if the task has no executions
			if (result[1] != null) {
				task.setTatalExecTime(((Long) result[1]).intValue());
			}
			if (result[2] != null) {
				task.setProgress(((Long) result[2]).intValue());
			}
			task.setLatestExecDate((Date) result[3]);
		}
		return task;
	}

}
